package com.ming.common.dynamic.spring.dynamic;

import cn.hutool.core.map.MapUtil;
import com.ming.common.dynamic.code.util.ClassUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.stereotype.Component;

import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author moon
 */
@Slf4j
@Component
public class MemoryClassLoader extends URLClassLoader {

    /**
     * 缓存字节码
     */
    private Map<String, byte[]> classBytesMap = new ConcurrentHashMap<>();

    /**
     * 构造
     */
    public MemoryClassLoader() {
        super(new URL[0], MemoryClassLoader.class.getClassLoader());
    }

    /**
     * 注册 Java 字符串到内存类加载器中
     *
     * @param className 类名字
     * @param javaStr   Java字符串
     */
    public void registerJava(String className, String javaStr) {
        try {
            this.classBytesMap.putAll(compile(className, javaStr));
        } catch (Exception e) {
            log.error("register java class exception:");
        }
    }

    public Map<String,String> registerJava(String javaStr) {
        String className = getClassName(javaStr);
        String beanName = className.substring(0,1).toLowerCase()+className.substring(1);
        String packageName = ClassUtils.getPackageName(javaStr);
        String fullClassName = packageName+"."+className;
        try {
            this.classBytesMap.putAll(compile(fullClassName, javaStr));
        } catch (Exception e) {
            log.error("register java class exception:");
        } finally {
            return MapUtil.builder(new HashMap<String,String>())
                    .put("className",className)
                    .put("packageName",packageName)
                    .put("beanName",beanName)
                    .put("fullClassName",fullClassName)
                    .build();
        }
    }

    private final Pattern CLASS_PATTERN = Pattern.compile("class\\s+([$_a-zA-Z][$_a-zA-Z0-9]*)\\s*");

    private String getClassName(String javaCode){
        Matcher matcher = this.CLASS_PATTERN.matcher(javaCode);
        if (matcher.find()) {
            return matcher.group(1);
        } else {
            throw new IllegalArgumentException("No valid class");
        }
    }

    /**
     * 编译 Java 源码
     *
     * @param className 类名字
     * @param javaStr   Java代码
     * @return class 二进制
     */
    private Map<String, byte[]> compile(String className, String javaStr) {
        //初始化编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //获取Java文件管理器
        try (MemoryJavaFileManager manager = new MemoryJavaFileManager()) {
            JavaFileObject javaFileObject = manager.makeStringSource(className, javaStr);
            DiagnosticCollector<JavaFileObject> compileCollector = new DiagnosticCollector();
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, compileCollector, null, null, Arrays.asList(javaFileObject));
            if (task.call()) {
                return manager.getClassBytes();
            }
        } catch (Exception e) {
            log.error("compile java str exception:",e);
        }
        return null;
    }

    /**
     * 获取 Class
     * @param name the name of the class
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] buf = classBytesMap.get(name);
        if (buf == null) {
            return super.findClass(name);
        }
        classBytesMap.remove(name);
        return defineClass(name, buf, 0, buf.length);
    }

    /**
     * 获取jar包所在路径
     *
     * @return jar包所在路径
     */
    public static String getPath() {
        ApplicationHome home = new ApplicationHome(MemoryJavaFileManager.class);
        String path = home.getSource().getPath();
        return path;
    }

    /**
     * 判断是否jar模式运行
     *
     * @return
     */
    public static boolean isJar() {
        return getPath().endsWith(".jar");
    }

}

